06 函数重构

函数重构

Extract Method(提炼方法)

problem:有一个可以组合在一起的代码片段。一大串代码,将想要注释的地方提炼成新的方法。
solution:将想要注释的地方提炼成新的方法,并且给一个好的命名,这点很重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void printOwing(double amount) {
printBanner();
//print details
System.out.println ("name:" + _name);
System.out.println ("amount" + amount);
}

//======================after refactoring=========================

void printOwing(double amount) {
printBanner();
printDetails(amount);
}
void printDetails (double amount) {
System.out.println ("name:" + _name);
System.out.println ("amount" + amount);
}

重构原因?

  1. 函数的粒度更小,被复用的几率更大。
  2. 命名好的函数,可以通过读名字就知道函数的作用,提高阅读效率。

Inline Method(内联方法)

problem:当方法体比方法本身更明显时,请使用此技术。
solution:在使用临时变量的地方,直接用给这个变量赋值的表达式代替。

1
2
3
4
5
6
7
8
9
10
11
12
int getRating() {
return (moreThanFiveLateDeliveries()) ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return _numberOfLateDeliveries > 5;
}

//======================after refactoring=========================

int getRating() {
return (_numberOfLateDeliveries > 5) ? 2 : 1;
}

重构原因?

并不是所有的语句都需要Extract Method,已经足够简单的不需要。

Inline Temp(内联变量)

problem:有一个临时变量,它分配了一个简单表达式的结果,仅此而已。
solution:用表达式本身替换对变量的引用。

1
2
3
4
5
6
7
8
9
10
boolean hasDiscount(Order order) {
double basePrice = order.basePrice();
return basePrice > 1000;
}

//======================after refactoring=========================

boolean hasDiscount(Order order) {
return order.basePrice() > 1000;
}

重构原因?

  1. 并不是说所有的表达式都需要使用Extract Variable,已经足够简单的不需要。
  2. 减少局部变量,从而减少对其他重构的限制

    我的理解是通过调用方法来获取值,解释为查询。Inline Temp不是必须的,它的原因是使用它的前提。

Replace Temp with Query(以查询代替临时变量)

problem:将表达式的结果放在局部变量中,以便以后在代码中使用。
solution:将整个表达式移动到单独的方法并从中返回结果。查询方法而不是使用变量。如有必要,将新方法合并到其他方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
double basePrice = _quantity * _itemPrice;
if (basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;

//======================after refactoring=========================

if (basePrice() > 1000)
return basePrice() * 0.95;
else
return basePrice() * 0.98;
...
double basePrice() {
return _quantity * _itemPrice;
}

重构原因?

  1. 以后变量的赋值逻辑可能会在多个不同方法中使用
  2. 如果想要使用Extract Method重构时,减少局部变量的限制,希望临时变量能在原来的方法和新方法中使用,这时候可以使用Replace Temp with Query。

变量要求只被赋值一次?

因为如果被赋值多次,那么“提取右侧表达式”时应该选择哪一个呢?
这时候就需要采用其他的重构手法来解决了。

代码清晰和性能的抉择?

使用Replace Temp with Query会导致多次调用赋值表达式的逻辑,这会造成性能上的一个损失,但是一般不需要从性能上考虑,真的出现性能问题再反向处理也是十分容易的。

Introduce Explaining Variable(引入解释性变量)

problem:有一个很难理解的表达。
solution:将表达式或其部分的结果放在不言自明的单独变量中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 if ( (platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}

//======================after refactoring=========================

final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
// do something
}

将一个复杂表达式的结果放进一个临时变量中,给一个好的命名以解释。

重构原因?

  1. 使用变量名可以给复杂的表达式一个基础的解释,提高代码的可读性。
  2. 当使用Extract Method比较麻烦的时候。

Split Temporary Variable(剖解临时变量)

problem:您有一个局部变量,用于在方法中存储各种中间值(循环变量除外)。
solution:对不同的值使用不同的变量。每个变量应该只负责一个特定的事情。

1
2
3
4
5
6
7
8
9
10
11
double temp = 2 * (_height + _width);
System.out.println (temp);
temp = _height * _width;
System.out.println (temp);

//======================after refactoring=========================

final double perimeter = 2 * (_height + _width);
System.out.println (perimeter);
final double area = _height * _width;
System.out.println (area);

什么是循环变量和结果收集变量?

循环变量:记录循环的周期的变量i
结果收集变量:用于累加、字符串接合、写入stream或者向群集(collection)添加元素

重构原因?

不是循环变量和结果收集变量,被赋值多次,承担了多个职责,令代码阅读者糊涂。

Remove Assignments to Parameters(移除对参数的赋值动作)

不要对参数进行赋值动作,以一个临时变量取代该参数的位置。

1
2
3
4
5
6
7
8
9
10
11
12
int discount (int inputVal, int quantity, int yearToDate) {
if (inputVal > 50) inputVal -= 2;
//...
}

//======================after refactoring=========================

int discount (int inputVal, int quantity, int yearToDate) {
int result = inputVal;
if (inputVal > 50) result -= 2;
//...
}

重构原因?

降低了代码的清晰度,而且混淆了pass by value(传值〕和pass by reference (传址)这两种参数传递方式。Java只采用pass by value传递方式。
在pass by value情况下,对参数的任何修改,都不会对调用端造成任何影响。那些用过pass by reference的人可能会在这一点上犯糊涂。
另一个让人糊涂的地方是函数本体内。如果你只以参数表示【被传递进来的东西】,那么代码会清晰得多,因为这种用法在所有语言中都表现出相同语义。

什么是pass by value?

含义是使它引用(参考、指涉、指向)另一个对象。
改变对象参数的内容是没有什么问题的。

Replace Method with Method Object(以函数对象取代函数)

problem:你有一个很长的方法,局部变量是如此交织在一起,你不能应用Extract Method。
solution:将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的值域(field) 然后你可以在同一个对象中将这个大型函数分解为数个小型函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Order {
//...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation.
//...
}
}

//======================after refactoring=========================

class Order {
//...
public double price() {
return new PriceCalculator(this).compute();
}
}

class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;

public PriceCalculator(Order order) {
// copy relevant information from order object.
//...
}

public double compute() {
// long computation.
//...
}
}

为什么需要这么做?

如果一个函数之中局部变量泛滥成灾, 那么想分解这个函数是非常困难的。Replace Temp with Query 可以助你减轻这一负担,但有时候你会发现根本无法拆解一个需要拆解的函数。

如何操作?

  • 建立一个新class,根据「待被处理之函数」的用途,为这个class命名。
  • 在新class中建立一个final值域,用以保存原先大型函数所驻对象。我们将这个值域称为extract class「源对象」。同时,针对原(旧)函数的每个临时变量和每个参 数,在新中建立一个个对应的值域保存之。
  • 在新class中建立一个构造函数(constructor),接收源对象及原函数的所有参数作为参数。
  • 在新class中建立一个compute()函数。
  • 将原(旧)函数的代码拷贝到compute()函数中。如果需要调用源对象的任何函数,请以「源对象」值域调用。
  • 编译。
  • 将旧函数的函数本体替换为这样一条语句:「创建上述新的一个新对象, 而后调用其中的compute()函数」。

Substitute Algorithm(替换你的算法)

把某个算法替换为另一个更清晰的算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
String foundPerson(String[] people){
for (int i = 0; i < people.length; i++) {
if (people[i].equals ("Don")){
return "Don";
}
if (people[i].equals ("John")){
return "John";
}
if (people[i].equals ("Kent")){
return "Kent";
}
}
return "";
}

//======================after refactoring=========================

String foundPerson(String[] people){
List candidates = Arrays.asList(new String[] {"Don", "John", "Kent"});
for (int i=0; i<people.length; i++)
if (candidates.contains(people[i]))
return people[i];
return "";
}

本文标题:06 函数重构

文章作者:Sun

发布时间:2019年01月08日 - 11:01

最后更新:2019年01月17日 - 20:01

原始链接:https://sunyi720.github.io/2019/01/08/refactoring/06 函数重构/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。